module lpuart_rx(
  input             uart_clk,
  input             uart_rst_n,

  input             baud_clk,
  input             baud_clk_offset,

  input             rxd_sync,

  input             stop_1bit,
  input             stop_2bit,
  input             frame_7bit,
  input             frame_8bit,
  input             frame_9bit,
  input             pce,
  input             ps,
  input             msbfirst,
  input             fifo_en,

  input             rx_en,
  input             mmrq,
  input      [7:0]  wake_addr,
  input             wake_addrm7,
  input             wake_method,
  input             uesm,
  input             clr_wakeup,
  input             clr_char_match,
  input             clr_idle,
  input      [1:0]  lp_wus,

  output            baud_clk_start,

  output            rx_push,
  output     [11:0] rx_push_data,
  input             rx_wfull,

  output reg        lp_wake_up_irq,
  output reg        rxne,
  output reg        rwu,
  output reg        rx_busy,
  output            rx_finish,
  output     [11:0] rx_data
);

localparam ST_IDLE         =  4'h0;
localparam ST_RX_START     =  4'h1;
localparam ST_RX_DATA0     =  4'h2;
localparam ST_RX_DATA1     =  4'h3;
localparam ST_RX_DATA2     =  4'h4;
localparam ST_RX_DATA3     =  4'h5;
localparam ST_RX_DATA4     =  4'h6;
localparam ST_RX_DATA5     =  4'h7;
localparam ST_RX_DATA6     =  4'h8;
localparam ST_RX_DATA7     =  4'h9;
localparam ST_RX_DATA8     =  4'ha;
localparam ST_RX_PARITY    =  4'hb;
localparam ST_RX_STOP1     =  4'hc;
localparam ST_RX_STOP2     =  4'hd;

// detect start machine
reg [3:0] det_cur_st;
reg [3:0] det_nxt_st;
wire      judge_signal;

assign judge_signal = rxd_sync;

assign baud_clk_start = ((det_cur_st == ST_IDLE) && (det_nxt_st == ST_RX_START));

//------------
// RX FSM
//------------
always@(posedge uart_clk, negedge uart_rst_n) begin:RX_FSM_SEQ_PROC
  if(!uart_rst_n)
    det_cur_st <= ST_IDLE;
  else if(~rx_en)
    det_cur_st <= ST_IDLE;
  else
    det_cur_st <= det_nxt_st;
end

always@(*) begin:RX_FSM_PROC
  det_nxt_st = det_cur_st;
  case(det_cur_st)
    ST_RX_START:begin
      if(baud_clk)
        det_nxt_st = ST_RX_DATA0;
    end
    ST_RX_DATA0:begin
      if(baud_clk)
        det_nxt_st = ST_RX_DATA1;
    end
    ST_RX_DATA1:begin
      if(baud_clk)
        det_nxt_st = ST_RX_DATA2;
    end
    ST_RX_DATA2:begin
      if(baud_clk)
        det_nxt_st = ST_RX_DATA3;
    end
    ST_RX_DATA3:begin
      if(baud_clk)
        det_nxt_st = ST_RX_DATA4;
    end
    ST_RX_DATA4:begin
      if(baud_clk)
        det_nxt_st = ST_RX_DATA5;
    end
    ST_RX_DATA5:begin
      if(baud_clk)begin
        if(frame_7bit & pce)
          det_nxt_st =  ST_RX_PARITY;
        else
          det_nxt_st =  ST_RX_DATA6;
      end
    end
    ST_RX_DATA6:begin
      if(baud_clk)begin
        if(frame_7bit) begin
          det_nxt_st = ST_RX_STOP1;
        end 
        else if(frame_8bit & pce)
          det_nxt_st =  ST_RX_PARITY;
        else
          det_nxt_st =  ST_RX_DATA7;
      end
    end
    ST_RX_DATA7:begin
      if(baud_clk)
        if(frame_8bit) begin
          det_nxt_st = ST_RX_STOP1;
        end 
        else if(frame_9bit & pce)
          det_nxt_st =  ST_RX_PARITY;
        else
          det_nxt_st =  ST_RX_DATA8;
    end
    ST_RX_DATA8:begin
      if(baud_clk)
        det_nxt_st = ST_RX_STOP1;
    end
    ST_RX_PARITY:begin
      if(baud_clk)
        det_nxt_st = ST_RX_STOP1;
    end
    ST_RX_STOP1:begin
      if(baud_clk)begin
        if(stop_2bit)
          det_nxt_st = ST_RX_STOP2;
        else if(stop_1bit)begin
          if(!rxd_sync)
            det_nxt_st = ST_RX_START;
          else
            det_nxt_st = ST_IDLE;
        end
      end
    end
    ST_RX_STOP2:begin
      if(baud_clk)
        if(!rxd_sync)
          det_nxt_st = ST_RX_START;
        else
          det_nxt_st = ST_IDLE;
    end
    default:// ST_IDLE
      if((rxd_sync == 1'b0))
        det_nxt_st = ST_RX_START;
      else
        det_nxt_st = ST_IDLE;
  endcase
end

wire shift_en;
assign shift_en = (det_cur_st == ST_RX_DATA0 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA1 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA2 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA3 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA4 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA5 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA6 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA7 && baud_clk_offset) ||
                  (det_cur_st == ST_RX_DATA8 && baud_clk_offset);

reg [11:0]  littleend_rx_shift_reg;
reg [11:0]  bigend_rx_shift_reg;
wire [11:0] rx_shift_reg;
reg         noise_err;
reg         frame_err;
wire        parity_err;
always@(posedge uart_clk, negedge uart_rst_n)begin:RX_SHIFT_REG_PROC0
  if(!uart_rst_n)begin
    littleend_rx_shift_reg <= 12'h0;
  end
  else begin
    if(pce) begin
      if(shift_en) begin
        case({frame_9bit, frame_8bit, frame_7bit})
          3'b001 : littleend_rx_shift_reg <= {3'h0, judge_signal, littleend_rx_shift_reg[5:1]};
          3'b010 : littleend_rx_shift_reg <= {2'h0, judge_signal, littleend_rx_shift_reg[6:1]};
          3'b100 : littleend_rx_shift_reg <= {1'b0, judge_signal, littleend_rx_shift_reg[7:1]};
          default: littleend_rx_shift_reg <= {2'b0, judge_signal, littleend_rx_shift_reg[6:1]};
        endcase
      end
    end 
    else begin
      if(shift_en) begin
        case({frame_9bit, frame_8bit, frame_7bit})
          3'b001 : littleend_rx_shift_reg <= {2'h0, judge_signal, littleend_rx_shift_reg[6:1]};
          3'b010 : littleend_rx_shift_reg <= {1'h0, judge_signal, littleend_rx_shift_reg[7:1]};
          3'b100 : littleend_rx_shift_reg <= {judge_signal, littleend_rx_shift_reg[8:1]};
          default: littleend_rx_shift_reg <= {1'b0, judge_signal, littleend_rx_shift_reg[7:1]};
        endcase
      end
    end
    littleend_rx_shift_reg[11:9] <= {noise_err, frame_err, parity_err};
  end
end
always@(posedge uart_clk, negedge uart_rst_n)begin:RX_SHIFT_REG_PROC1
  if(!uart_rst_n)begin
    bigend_rx_shift_reg <= 12'h0;
  end
  else begin
    if(pce) begin
      if(shift_en) begin
        case({frame_9bit, frame_8bit, frame_7bit})
          3'b001 : bigend_rx_shift_reg <= {3'h0, bigend_rx_shift_reg[4:0], judge_signal};
          3'b010 : bigend_rx_shift_reg <= {2'h0, bigend_rx_shift_reg[5:0], judge_signal};
          3'b100 : bigend_rx_shift_reg <= {1'b0, bigend_rx_shift_reg[6:0], judge_signal};
          default: bigend_rx_shift_reg <= {2'b0, bigend_rx_shift_reg[5:0], judge_signal};
        endcase
      end
    end 
    else begin
      if(shift_en) begin
        case({frame_9bit, frame_8bit, frame_7bit})
          3'b001 : bigend_rx_shift_reg <= {2'h0, bigend_rx_shift_reg[5:0], judge_signal};
          3'b010 : bigend_rx_shift_reg <= {1'h0, bigend_rx_shift_reg[6:0], judge_signal};
          3'b100 : bigend_rx_shift_reg <= {bigend_rx_shift_reg[7:0], judge_signal};
          default: bigend_rx_shift_reg <= {1'b0, bigend_rx_shift_reg[6:0], judge_signal};
        endcase
      end
    end
    bigend_rx_shift_reg[11:9] <= {noise_err, frame_err, parity_err};
  end
end

assign rx_shift_reg = msbfirst ? bigend_rx_shift_reg : littleend_rx_shift_reg;
assign rx_data = {noise_err, frame_err, parity_err, rx_shift_reg[8:0]};
assign rx_push_data = rx_data;
assign rx_push = fifo_en & rx_finish & (~rx_wfull);

wire rx_last_bit = ((det_cur_st == ST_RX_STOP1) && (det_nxt_st == ST_IDLE)) ||
                   ((det_cur_st == ST_RX_STOP2) && (det_nxt_st == ST_IDLE)) ||
                   ((det_cur_st == ST_RX_STOP1) && (det_nxt_st == ST_RX_START)) ||
                   ((det_cur_st == ST_RX_STOP2) && (det_nxt_st == ST_RX_START));

assign rx_finish = rx_last_bit;

wire start_det = (det_cur_st == ST_RX_START);
always@(posedge uart_clk, negedge uart_rst_n) begin
  if(!uart_rst_n)
   rx_busy <= 1'b0;
  else if(start_det)
   rx_busy <= 1'b1;
  else if(rx_finish)
   rx_busy <= 1'b0;
end

assign check_parity = ((det_cur_st == ST_RX_PARITY) && baud_clk_offset);

reg even_parity;
always@(*)begin:CHECK_PARITY_PROC
  if(pce && check_parity) begin
    even_parity = judge_signal^ (^rx_shift_reg[8:0]);
  end else
    even_parity = 1'b0;
end

assign odd_parity  = pce ? ~even_parity : 1'b0;
assign parity_err = ps ? odd_parity : even_parity;

//frame errror, not detect a valid stop bit
always@(*)begin
  frame_err = 1'b0;
  if(rx_finish)begin
    if(((det_cur_st == ST_RX_STOP1) && (det_nxt_st == ST_IDLE) && stop_1bit) ||
      ((det_cur_st == ST_RX_STOP2) && (det_nxt_st == ST_IDLE) && stop_2bit))
      frame_err = ~judge_signal;
    else
      frame_err = rx_shift_reg[10];
  end
end

always@(uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)
    noise_err <= 1'b0;
  else if(~rx_en || rwu)
    noise_err <= 1'b0;
  else if(baud_clk && (det_cur_st == ST_RX_START) && baud_clk_offset)
    noise_err <= ~judge_signal; // if start bit is '1', generate noise_err
end

// break frame
reg det_break_frame;
reg det_idle_frame;
always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)begin
    det_break_frame <= 1'b0;
  end else if(rx_finish)begin
    if((det_cur_st == ST_RX_STOP2) && (rx_shift_reg[8:0] == 9'h0))
      det_break_frame <= 1'b1;
  end
  else
    det_break_frame <= 1'b0;
end

// idle frame
always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)begin
    det_idle_frame <= 1'b0;
  end else if(rx_finish)begin
    if(pce)begin
      case({frame_9bit, frame_8bit, frame_7bit})
        3'b001: if(rx_shift_reg[5:0] == 6'h0) det_idle_frame <= 1'b1;
        3'b010: if(rx_shift_reg[6:0] == 7'h0) det_idle_frame <= 1'b1;
        3'b100: if(rx_shift_reg[7:0] == 8'h0) det_idle_frame <= 1'b1;
      endcase
    end else begin
      case({frame_9bit, frame_8bit, frame_7bit})
        3'b001: if(rx_shift_reg[7:0] == 7'h0) det_idle_frame <= 1'b1;
        3'b010: if(rx_shift_reg[8:0] == 8'h0) det_idle_frame <= 1'b1;
        3'b100: if(rx_shift_reg[9:0] == 9'h0) det_idle_frame <= 1'b1;
      endcase
    end
  end else
    det_idle_frame <= 1'b0;
end


reg wakeup_en;
always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)
    wakeup_en <= 1'b0;
  else if (((det_cur_st == ST_RX_STOP1) && (det_nxt_st == ST_IDLE)) ||
           ((det_cur_st == ST_RX_STOP2) && (det_nxt_st == ST_IDLE)))
    wakeup_en <= 1'b0;
  else if (baud_clk_offset && frame_9bit && det_cur_st == ST_RX_DATA7 && pce)
    wakeup_en <= judge_signal;
  else if (baud_clk_offset && frame_8bit && det_cur_st == ST_RX_DATA6 && pce)
    wakeup_en <= judge_signal;
  else if (baud_clk_offset && frame_7bit && det_cur_st == ST_RX_DATA5 && pce)
    wakeup_en <= judge_signal;
  else if (baud_clk_offset && frame_9bit && det_cur_st == ST_RX_DATA8 && ~pce)
    wakeup_en <= judge_signal;
  else if (baud_clk_offset && frame_8bit && det_cur_st == ST_RX_DATA7 && ~pce)
    wakeup_en <= judge_signal;
  else if (baud_clk_offset && frame_7bit && det_cur_st == ST_RX_DATA6 && ~pce)
    wakeup_en <= judge_signal;
end

reg addr_wakeup_en;
always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n) 
    addr_wakeup_en <= 1'b0;
  else if(((det_cur_st == ST_RX_STOP1) && (det_nxt_st == ST_IDLE)) ||
          ((det_cur_st == ST_RX_STOP2) && (det_nxt_st == ST_IDLE)))
    addr_wakeup_en <= wakeup_en;
  else if(det_cur_st == ST_IDLE)
    addr_wakeup_en <= 1'b0;
end

wire wake_ilde_line = (wake_method == 1'b0);
always@(posedge uart_clk, negedge uart_rst_n)begin:RWU_PROC
  if(!uart_rst_n) 
    rwu <= 1'b0;
  else if(mmrq)
    rwu <= 1'b0;
  else if(wake_ilde_line)begin
    if(det_idle_frame)
      rwu <= 1'b1;
    else
      rwu <= 1'b0;
  end
  else begin// wake_addrss_mask
    if(addr_wakeup_en)begin
      if(wake_addrm7)begin
        if(rx_data[6:0] == wake_addr[6:0])
          rwu <= 1'b1;
        else
          rwu <= 1'b0;
      end
      else begin// ~wake_addrm7
        if(rx_data[3:0] == wake_addr[3:0])
          rwu <= 1'b1;
        else
          rwu <= 1'b0;
      end
    end
  end
end

reg lpuart_in_mute_mode;
always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)
    lpuart_in_mute_mode <= 1'b0;
  else if(mmrq)
    lpuart_in_mute_mode <= 1'b1;
end

wire lp_mode = 1'b0; // TODO
wire wuf_start_det = lp_mode & start_det;

always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)
    rxne <= 1'b0;
  else if(rx_finish)
    rxne <= 1'b1;
  else if(start_det)
    rxne <= 1'b0;
end

always@(*)begin
  case(lp_wus)
    2'b00:lp_wake_up_irq = addr_wakeup_en;
    2'b10:lp_wake_up_irq = wuf_start_det;
    2'b11:lp_wake_up_irq = rxne;
    default: lp_wake_up_irq = 1'b0;
  endcase
end

endmodule
